#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstdlib>
#include "Common.hpp"
#include "Mutex.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
// 封装我们对应的套接字
namespace SocketModule
{
    using namespace LogModule;
    const static int gbacklog = 16;
    // 模版方法模式

    // 基类socket,大部分方法都是纯虚方法
    class Socket
    {
    public:
        virtual ~Socket() {} // 析构函数

        // 创建套接字的时候我们是需要得到一个文件描述符的
        virtual void SocketOrDie() = 0; // 纯虚函数

        virtual void BindOrDie(uint16_t port) = 0; // 绑定接口

        virtual void ListenOrDie(int backlog) = 0; // 监听接口

        virtual int  Accept(InetAddr *client) = 0; // 接收连接

        virtual void Close() = 0; // 关闭套接字

        virtual int Recv(std::string *out) = 0; // 接收数据

        virtual int Send(const std::string &message) = 0; // 发送数据

        virtual int Connect(const std::string&server_ip,uint16_t port) = 0; // 连接服务器
        
        // 获取文件描述符
        virtual int Fd() const = 0; // 获取套接字文件描述符
    public:
        // 创建一个listen套接字的方法
        void BuildTcpSocketMethod(uint port, int backlog = gbacklog) // 监听队列的长度直接缺省设置为16
        {
            SocketOrDie();        // 创建套接字
            BindOrDie(port);      // 绑定
            ListenOrDie(backlog); // 监听
        }
        // void BuildUdpSocketMethod()
        // {
        //     SocketOrDie(); // 创建套接字
        //     BindOrDie();   // 绑定
        // }

        void BuildClientySocketMethod()
        {
            SocketOrDie();        // 创建套接字
        }
    };
    const static int defaultfd = -1;

    class TcpSocket : public Socket
    {
    public:
        TcpSocket() // 无参
            : _sockfd(defaultfd)
        {
        }
        TcpSocket(int fd) // 对构造函数进行重载操作
            : _sockfd(fd)
        {
        }
        ~TcpSocket() {} // 析构函数

        // 创建套接字的时候我们是需要得到一个文件描述符的
        void SocketOrDie() override // 纯虚函数
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0) // 创建套接字失败了
            {
                LOG(LogLevel::ERROR) << " socket error";
                exit(SOCKET_ERROR);
            }
            //可以保证我们客户端立即进行重启操作，不会出现bind erroe的问题了
            int opt=1;
            setsockopt(_sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
            LOG(LogLevel::INFO) << " socket success";
        }

        // 通过传入端口号进行绑定操作
        void BindOrDie(uint16_t port) override // 绑定接口
        {
            // 传一个端口号就行了
            InetAddr localaddr(port);
            // 绑定对应的套接字
            int n = ::bind(_sockfd, localaddr.NetAddrptr(), localaddr.NetAddrlen());
            if (n < 0) // 绑定失败了
            {
                LOG(LogLevel::ERROR) << " bind error";
                exit(SOCKET_ERROR);
            }
            LOG(LogLevel::INFO) << " bind success";
        }

        void ListenOrDie(int backlog) override // 监听接口
        {
            int n = ::listen(_sockfd, backlog);
            if (n < 0) // 监听失败了
            {
                LOG(LogLevel::FATAL) << " listn error";
                exit(LISTEN_ERROR);
            }
            LOG(LogLevel::INFO) << " listn success";
        }

        // 返回值是一个套接字
        //std::shared_ptr<Socket> Accept(InetAddr *client) override // 接收连接
        int  Accept(InetAddr *client) override // 接收连接
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int fd = ::accept(_sockfd, CONV(peer), &len);
            if (fd < 0) // 接收失败了
            {
                // 文件描述符小于0说明链接失败了
                LOG(LogLevel::WARING) << " accept WARNING...";
                return -1;
            }
            return fd;
            // 这里就说明成功了
        //     client->SetAddr(peer);
        //     return std::make_shared<TcpSocket>(fd); // 返回的是一个TcpSocket套接字对象
        }
        // 我们调用accept的时候，返回的是一个基类的指针指向子类方法
        

        int Recv(std::string *out) override // 接收数据
        {
            //从文件描述符中进行流式读取，不关心读到的是什么
            char buffer[4096*2];
            //文件描述符已经构造到了_sockfd中了
            //读取了
            // recv是专门给套接字进行读取的接口，最后一个读取方式设置为0
            ssize_t  n=::recv(_sockfd, buffer,sizeof(buffer)-1,0);//读到buffer中，
            if(n>0)//读成功了
            {
                buffer[n]=0;//最后一个字符置为0
                *out+=buffer; //将buffer中的内容直接追加到buffer中            
            }

            return n;//返回读取的字节数
           
        }
        

        int Send(const std::string &message) override // 发送数据
        {
            //传进来的字符串，我们直接send过去

            //往_sockfd中写入message的内容,写入方式的话我们设置为0
            return send(_sockfd, message.c_str(), message.size(),0);
           
        }

        void Close()override
        {
            if (_sockfd >= 0)
                ::close(_sockfd); // 将文件描述符直接关掉就行了
        }
        int Connect(const std::string&server_ip,uint16_t port) override // 连接服务器
        {
            InetAddr server(server_ip,port);//主机转网络地址
            return ::connect(_sockfd,server.NetAddrptr(),server.NetAddrlen());
        }

        // 获取文件描述符
        int Fd() const
        {
            return _sockfd;
        }

    private:
        int _sockfd; // 套接字文件描述符
    };

    // class UdpSocket : public Socket
    // {

    // };
}